MaxModLoader.Create(MaxMod_Loader_XM,"Fasttracker ~q.xm~q","xm")

Function MaxMod_Loader_XM:TMusic(url:Object)
 
	Local ThisMOD:MaxMod_Mixer = New MaxMod_Mixer 
	Local tracker$
	Local samplescount:Int
	Local txt$
	Local songname$
	Local bigpat:Int
	Local length:Int
	Local freq:Int
	Local sampleno:Int
	Local samplefx:Int,samplefxvalue:Int
	Local value$
	Local c:Int,z:Int
    Local m:Int,d:Int,pos:Int
	Local val:Int

	Local f:TStream
	If TStream(Url)
		f = TStream(Url)
	Else
    	f = ReadStream(url)		; If Not f Return Null
	EndIf

	txt$ 					= Trim(ReadString(f,17))	'; Print txt$											' Should read 'Extended Module:'
	If txt$<>"Extended Module:" Then Return Null 

	ThisMod.SongName 		= Trim(ReadString(f,20)) 	'; Print "Module name = ~q"+ThisMod.SongName+"~q" 		' The Title of the module
	val 					= ReadByte(f)				'; Print Hex$(Val)										' wasted byte should be $1a
	ThisMod.TrackerName 	= Trim(ReadString(f,20))	'; Print "Tracker name = ~q"+ThisMod.TrackerName$+"~q"	' Tracker name
	val 					= ReadByte(f)				'; Print "$"+Right(Hex$(Val),2)							' minor version bit ($04)
	val 					= ReadByte(f)				'; Print "$"+Right(Hex$(Val),2)							' major version bit ($01)
	val						= ReadInt(f)				'; Print "Song header size = "+val
	Local patternseek:Int	= StreamPos(f)+val-4
	Local songlength:Int	= ReadShort(f) 				'; Print "Song length = "+songlength					' Song length (in patten order table)
	val 					= ReadShort(f) 				'; Print "Restart position = "+Val						' Restart position
	ThisMOD.ActiveChannels 	= ReadShort(f) 				'; Print "Number of channels = "+ThisMOD.ActiveChannels ' Number of channels
	bigpat 					= ReadShort(f)				'; Print "Number of patterns = "+bigpat	 				' Number of patterns
	Local instcount:Int		= ReadShort(f)				'; Print "Number of instruments = "+instcount 			' Number of instruments 
	ThisMOD.PeriodMode 		= ReadShort(f)				'; Print "Period mode = "+ThisMOD.PeriodMode			' flags 0=amiga 1=linear
	ThisMOD.DefaultSpeed 	= ReadShort(f) 				'; Print "tempo = "+ThisMOD.DefaultSpeed 				' tempo
	ThisMOD.DefaultTempo 	= ReadShort(f) 				'; Print "bpm = "+ThisMOD.DefaultTempo	 				' bpm
	ThisMOD.Speed 			= ThisMod.DefaultSpeed
	ThisMOD.Tempo 			= ThisMod.DefaultTempo
	ThisMOD.ActiveSamples	= instcount
	ThisMOD.Init(ThisMOD.ActiveChannels)
		
	'    +20    256	 (Byte) Pattern order table
	Local n:Int,usepattern:Int
	ThisMOD.SongPattern = New Int[songlength+1]
	For n=0 To songlength
        usepattern = ReadByte(f)
        ThisMOD.SongPattern[n] = usepattern
		If usepattern>(bigpat-1) Then ThisMod.Pattern[usepattern] = MaxMod_Pattern.Create(ThisMOD,ThisMOD.ActiveChannels, 64)
    Next
    ThisMOD.ActiveSongPatterns 	= songlength-1 ';bigpat
    ThisMOD.CurrentPattern 		= ThisMOD.SongPattern[0]
	ThisMOD.ActiveSequences 	= BigPat

	
'	' PATTERN DATA
	Local seq:Int, nextseq:Int
	Local patterndataseek:Int
	Local numberofrows:Int
	Local packedsize:Int, packingtype:Int
	Local bval:Int
	Local inst:Int,vol:Int,ef:Int,efp:Int,lvol:Int
	Local tempfilepos:Int

	ThisMod.Pattern = New MaxMod_Pattern[bigpat]
	
    For seq = 0 Until bigpat

		SeekStream(f,patternseek)

		' PATTERN HEADER
		val 			= ReadInt(f) 			'; Print "Pattern Header Length = "+Val				' size of the header (from here!)
		patterndataseek = StreamPos(f)+Val-4
		packingtype 	= ReadByte(f) 			'; Print "Packing Type = ~q"+packingtype+"~q "		' wasted byte should be $1a
		numberofrows 	= ReadShort(f) 			'; Print "Number of rows in pattern = "+numberofrows	' Number of rows in pattern
		packedsize 		= ReadShort(f) 			'; Print "Packed patterndata size = "+packedsize	' Packed patterndata size
		
		If packingtype>0 Then SeekStream(f,patternseek) ; Continue
		patternseek = patterndataseek + packedsize

		SeekStream(f,patterndataseek)
			
		If packedsize>0
	
	'        SeekStream(f,offset+( ((1024/4)*ThisMOD.ActiveChannels) *seq))
	
	'      ?      1	 	(Byte) Note (0-71, 0 = C-0)
	'					<< Hah. Actually note numbers are 1-96,
	'			        97 being the key-off note (Try To find
	'					that in the documentation! >>
	'     +1      1	 	(Byte) Instrument (0-128)
	'					<< More like 1-128 >>
	'     +2      1	 	(Byte) Volume column Byte (see below)
	'     +3      1	 	(Byte) Effect Type
	'     +4      1	 	(Byte) Effect parameter
	
			ThisMod.Pattern[seq] = MaxMod_Pattern.Create(ThisMod, ThisMOD.ActiveChannels, numberofrows)

	        For n=0 Until numberofrows
	            For c=0 Until ThisMOD.ActiveChannels
	
					Local Track:MaxMod_Track = ThisMod.Pattern[seq].Track[c]
					bval = ReadByte(f)
					If bval&%10000000
						If bval&%00000001 Track.Note[n] = ReadByte(f)-1	' note
						If bval&%00000010 Track.Inst[n] = ReadByte(f)	' instrument
						If bval&%00000100 Track.Vol[n]	= ReadByte(f)	' volume
						If bval&%00001000 Track.FX[n]	= ReadByte(f)	' effect
						If bval&%00010000 Track.FXV[n]	= ReadByte(f)	' effect parameter
					Else
						Track.Note[n] 	= bval-1
						Track.Inst[n] 	= ReadByte(f)
						Track.Vol[n]	= ReadByte(f)
						Track.FX[n]		= ReadByte(f)
						Track.FXV[n]	= ReadByte(f)
					EndIf
	            Next
	        Next
		EndIf

		ThisMod.Pattern[seq].UpdateText

	Next






	' --------- INSTRUMENTS HEADER

    '  ?      4	(dword) Instrument size << header that is >>
    ' +4     22	 (char) Instrument name
    '+26      1	 (Byte) Instrument Type (always 0) << This seems pretty random, don't assume it's zero >>
    '+27      2	 (word) Number of samples in instrument	
	Local instrumentname$

	ThisMod.Instrument		= New MaxMod_Instrument[instcount+1]
	
	Local instno:Int, reserved:Int
	For instno = 1 To instcount

		Local Inst:MaxMod_Instrument = New MaxMod_Instrument
		ThisMod.Instrument[instno] = Inst

		val = ReadInt(f)
		Local sampleheaderseek:Int = StreamPos(f)+Val-4
		
		instrumentname$ 	= Trim(ReadString(f,22))
		Inst.Name 			= Trim(instrumentname)
		Inst.InstrumentType = ReadByte(f)
		Inst.SampleCount 	= ReadShort(f)

		If Inst.SampleCount=0
		
			SeekStream(f,sampleheaderseek)
		
		ElseIf Inst.SampleCount>0

			Local sampledataseek:Int = StreamPos(f)
			val = ReadInt(f)
			sampledataseek:+Val
			
			For n=1 To 96
				Inst.NoteSample[n] = ReadByte(f)
				If Inst.NoteSample[n]=>Inst.SampleCount Then Inst.NoteSample[n]=0
			Next
			Local tick:Int,vol:Int
			For n=1 To 12 ; tick = ReadShort(f) ; vol  = ReadShort(f) ; Inst.AddVolumeEnvelopePoint(tick,vol) ; Next
			For n=1 To 48 ; Inst.PanEnvelope[n] = ReadByte(f) ; Next
			
			Inst.VolPoints 		= ReadByte(f)
			Inst.PanPoints 		= ReadByte(f)
			Inst.VolSustain 	= ReadByte(f)
			Inst.SetVolSustainPoint(Inst.VolSustain)
			Inst.VolLoopStart 	= ReadByte(f)							'; Print "Volume loop start point = "+Inst.VolLoopStart
			Inst.VolLoopEnd 	= ReadByte(f)							'; Print "Volume loop End point = "+Inst.VolLoopEnd
			Inst.SetVolLoopPoints(Inst.VolLoopStart,Inst.VolLoopEnd)
			Inst.PanSustain 	= ReadByte(f)							'; Print "Panning sustain point = "+Inst.PanSustain
			Inst.PanLoopStart 	= ReadByte(f)							'; Print "Panning loop start point = "+Inst.PanLoopStart
			Inst.PanLoopEnd 	= ReadByte(f)							'; Print "Panning loop End point = "+Inst.PanLoopEnd
			Inst.VolType 		= ReadByte(f)							'; Print "Volume Type = "+Inst.VolType
			Inst.PanType 		= ReadByte(f)							'; Print "Panning Type = "+Inst.PanType
			Inst.VibType 		= ReadByte(f)							'; Print "Vibrato Type = "+Inst.VibType
			Inst.VibSweep 		= ReadByte(f)							'; Print "Vibrato sweep = "+Inst.VibSweep
			Inst.VibDepth 		= ReadByte(f)							'; Print "Vibrato depth = "+Inst.VibDepth
			Inst.VibRate 		= ReadByte(f)							'; Print "Vibrato rate = "+Inst.VibRate
			Inst.VolFadeout 	= ReadShort(f)							'; Print "Volume fadeout = "+Inst.VolFadeout
			Inst.Reserved 		= ReadShort(f)							'; Print "Reserved = "+Inst.Reserved

			SeekStream(f,sampleheaderseek)
		
			Local Finetune:Int, volume64:Int, samheadn:Int, samdatan:Int
			For samheadn = 0 Until Inst.SampleCount
				Local Sam:MaxMod_Sample = New MaxMod_Sample
				Inst.Sample[samheadn] = Sam
'				Sam.Parent 		= Inst
				Sam.Length 		= ReadInt(f)
				Sam.LoopStart 	= ReadInt(f)
				Sam.LoopLength 	= ReadInt(f)
				volume64		= ReadByte(f)
				Sam.Volume		= (1!/64)*volume64
				finetune 		= ReadByte(f) 	; If finetune>127 Then finetune:-256
				Sam.Finetune	= (1!/128)*finetune
				Sam.Flag 		= ReadByte(f)
				Sam.Pan			= ReadByte(f) 	; If Sam.Pan>127 Then Sam.Pan:-128
				Sam.RelativeNote= ReadByte(f)	; If Sam.RelativeNote>127 Then Sam.RelativeNote:-256
				reserved		= ReadByte(f)
				Sam.name$		= Trim(ReadString(f,22))
			Next

			For samdatan = 0 Until Inst.SampleCount

				Local Sam:MaxMod_Sample = Inst.Sample[samdatan]
				Local oldv:Int=0, newv:Int=0
				Local DataBank:TBank 
				
				If Sam.flag&16
					DataBank:TBank = CreateBank(Sam.Length)
					For m=0 Until (Sam.Length/2)
						newv = ReadShort(f)
						newv:+oldv
						PokeShort(DataBank,m*2,newv)
						oldv = newv
					Next
					Sam.ConvertSampleFromBank(DataBank:TBank, 2)
					Sam.LoopStart:/2
					Sam.LoopLength:/2
				Else
					DataBank:TBank = CreateBank(Sam.Length)
					For m=0 Until Sam.Length
						newv = ReadByte(f)
						newv:+oldv
						PokeByte(DataBank,m,newv)
						oldv = newv
					Next
					Sam.ConvertSampleFromBank(DataBank:TBank, 1)
				EndIf 			

				If Sam.flag&1		' looping
					Sam.Loop = 1
				ElseIf Sam.flag&2	' pingpong
					Sam.Loop = 2
				Else				' no loop
					Sam.Loop = 0
				EndIf
			
			Next

		EndIf 

	Next
	CloseFile(f)
	
	' calculate length
	ThisMod.CalcSize
	ThisMod.MixLevel = 0.25
	
	Return ThisMOD

EndFunction





Function PadString$(txt$,length:Int,PadWith$=Chr$(0))
	Local result$
	If Len(txt$)=>length Then Return Left$(txt$,length)
	result$=txt$
	Repeat
		result$:+Chr$(0)
	Until Len(result$)=>length
	Return result
EndFunction
